﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Web;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Text.RegularExpressions;

namespace Job.Core
{
    /// <summary>
    /// 文件操作封装(所有相关路径均用虚拟路径)。
    /// </summary>
    public static class UploadFile
    {        

        #region Helper

        /// <summary>
        /// 以 KB/M/G 的格式返回指定字节长度字符串描述。
        /// </summary>
        /// <param name="len">长度。</param>
        /// <returns>长度文字描述字串。</returns>
        public static string FormatBytes(long len)
        {
            if (len < 0)
                throw new ArgumentOutOfRangeException("len");

            if (len > 1073741824)
                return (((double)len / 1073741824)).ToString(".00") + "G";
            if (len > 1048576)
                return (((double)len / 1048576)).ToString(".00") + "M";
            if (len > 1024)
                return (((double)len / 1024)).ToString(".00") + "KB";

            return len.ToString() + "B";
        }

        private static readonly Regex dataRegex = new Regex("%[^%]+%", RegexOptions.Compiled);
        /// <summary>
        /// 获取保存文件路径，并保证路存在。
        /// </summary>
        /// <param name="baseDirectory">基目录，可用%%包含时间字串格式来根据时间动态生成基目录，不传时将已当前 Web 应用程序根目录为基目录。</param>
        /// <param name="fileName">保存文件名。</param>
        /// <returns>保存文件虚拟路径(当存在多个相同文件名时在文件名后加 _d 后缀，如 file_1.jpg)</returns>
        public static string GetSavePath(string baseDirectory, string fileName)
        {            
            if (string.IsNullOrEmpty(fileName))
                throw new ArgumentNullException("fileName");

            //可能传的是全路径。
            int full = fileName.LastIndexOf('\\');
            if (full != -1)
                fileName = fileName.Substring(full + 1);

            if (string.IsNullOrEmpty(baseDirectory))
            {
                baseDirectory = "~/";
            }
            else
            {
                MatchCollection coll = dataRegex.Matches(baseDirectory);
                if (coll.Count > 0)
                {
                    //替换动态日期。
                    foreach (Match match in coll)
                        baseDirectory = baseDirectory.Replace(match.Value, DateTime.Now.ToString(match.Value.Trim('%')));
                }

                if (!baseDirectory.EndsWith("/"))
                    baseDirectory += "/";
            }

            //保证路径存在。
            string savePath = HttpContext.Current.Server.MapPath(baseDirectory);
            if (!Directory.Exists(savePath))
                Directory.CreateDirectory(savePath);

            //检查文件是否存在。
            string ext = Path.GetExtension(fileName);
            string saveFile = savePath + fileName;
            if (File.Exists(saveFile))
            {
                //存在时要取最大版本号。
                int maxVersion = 0;
                string tmpName = Regex.Replace(fileName, @"(?:_\d+)?\..*$", string.Empty);
                string[] existsFiles = Directory.GetFiles(savePath, tmpName + "_*" + ext);
                foreach (string file in existsFiles)
                {
                    Match match = Regex.Match(file, string.Format(@"_(\d+)\{0}$", ext));
                    if (match.Success)
                    {
                        int ver = int.Parse(match.Groups[1].Value);
                        if (maxVersion < ver) maxVersion = ver;
                    }
                }

                fileName = tmpName + "_" + (maxVersion + 1) + ext;
            }

            if (baseDirectory.StartsWith("~"))
                baseDirectory = HttpContext.Current.Request.ApplicationPath + baseDirectory.Substring(1);

            return baseDirectory + fileName;
        }

        /// <summary>
        /// 为文件添加后缀。
        /// </summary>
        /// <param name="path">路径。</param>
        /// <param name="suffix">后缀。</param>
        /// <returns>添加后缀后的字串。</returns>
        public static string AddSuffix(string path, string suffix)
        {
            if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(suffix))
                return path;

            int index = path.LastIndexOf('.');

            if (index == -1)
                return path + suffix;

            return path.Insert(index, suffix);
        }

        /// <summary>
        /// 为文件移除后缀。
        /// </summary>
        /// <param name="path">路径。</param>
        /// <param name="suffix">后缀。</param>
        /// <returns>移除后缀后的字串。</returns>
        public static string RemoveSuffix(string path, string suffix)
        {            
            if (string.IsNullOrEmpty(path) || string.IsNullOrEmpty(suffix))
                return path;

            return Regex.Replace(path, suffix + @"(\..*)$", "$1");
        }

        /// <summary>
        /// 从服务器删除一个文件。
        /// </summary>
        /// <param name="path">文件虚拟路径。</param>
        public static void Delete(string path)
        {
            File.Delete(HttpContext.Current.Server.MapPath(path));
        }

        #endregion

        #region Upload

        /// <summary>
        /// 执行文件上传。
        /// </summary>
        /// <param name="upload">FileUpload 控件。</param>
        /// <param name="basePath">保存虚拟路径基目录，可用%%包含时间字串格式来根据时间动态生成基目录，不传时将已当前 Web 应用程序根目录为基目录。</param>
        /// <param name="maxSize">文件大小上限(KB单位)，设为 null 不限制大小。</param>
        /// <param name="extFilter">支持扩展名，用|分格(如".jpg|.jpeg|.gif|.bmp|.png"),设置为空时不限制文件类型。</param>
        /// <param name="errorMessage">out 上传失败时的错误信息。</param>
        /// <returns>保存文件虚拟路径</returns>
        public static string UpFile(FileUpload upload, string basePath, int? maxSize, string extFilter, out string errorMessage)
        {
            errorMessage = string.Empty;

            if (string.IsNullOrEmpty(upload.FileName))
            {
                errorMessage = RS.GetString("NotUploadFiles");
                return string.Empty;
            }

            long fileSize = upload.FileContent.Length;

            if (fileSize == 0)
            {
                errorMessage = RS.GetString("UploadIsEmptyFile");
                return string.Empty;
            }

            //文件扩展名
            string fileExt = Path.GetExtension(upload.FileName);

            if (!string.IsNullOrEmpty(extFilter))
            {
                if (!Regex.IsMatch(fileExt, extFilter, RegexOptions.IgnoreCase))
                {
                    errorMessage = string.Format(RS.GetString("OnlySupportFileTypes"), extFilter);
                    return string.Empty;
                }
            }
            //检查大小
            if (maxSize.HasValue)
            {
                if (fileSize < 1 || fileSize > maxSize * 1024)
                {
                    errorMessage = string.Format(RS.GetString("FileSizeLimit"), FormatBytes(1024L * maxSize.Value));
                    return string.Empty;
                }
            }

            //保存文件
            string newPath = GetSavePath(basePath, upload.FileName);
            upload.PostedFile.SaveAs(HttpContext.Current.Server.MapPath(newPath));
            return newPath;
        }

        /// <summary>
        /// 执行文件上传。
        /// </summary>
        /// <param name="upload">HtmlInputFile控件。</param>
        /// <param name="basePath">保存虚拟路径基目录，可用%%包含时间字串格式来根据时间动态生成基目录，不传时将已当前 Web 应用程序根目录为基目录。</param>
        /// <param name="maxSize">文件大小上限(KB单位)，设为 null 不限制大小。</param>
        /// <param name="extFilter">支持扩展名，用|分格(如".jpg|.jpeg|.gif|.bmp|.png"),设置为空时不限制文件类型。</param>
        /// <param name="errorMessage">out 上传失败时的错误信息。</param>
        /// <returns>保存文件虚拟路径</returns>
        public static string UpFile(HtmlInputFile upload, string basePath, int? maxSize, string extFilter, out string errorMessage)
        {
            errorMessage = string.Empty;

            if (string.IsNullOrEmpty(upload.Value))
            {
                errorMessage = RS.GetString("NotUploadFiles");
                return string.Empty;
            }

            long fileSize = upload.PostedFile.ContentLength;

            if (fileSize == 0)
            {
                errorMessage = RS.GetString("UploadIsEmptyFile");
                return string.Empty;
            }

            //文件扩展名
            string fileExt = Path.GetExtension(upload.PostedFile.FileName);

            if (!string.IsNullOrEmpty(extFilter))
            {
                if (!Regex.IsMatch(fileExt, extFilter, RegexOptions.IgnoreCase))
                {
                    errorMessage = string.Format(RS.GetString("OnlySupportFileTypes"), extFilter);
                    return string.Empty;
                }
            }
            //检查大小
            if (maxSize.HasValue)
            {
                if (fileSize < 1 || fileSize > maxSize * 1024)
                {
                    errorMessage = string.Format(RS.GetString("FileSizeLimit"), FormatBytes(1024L * maxSize.Value));
                    return string.Empty;
                }
            }

            //保存文件
            string newPath = GetSavePath(basePath, upload.PostedFile.FileName);
            upload.PostedFile.SaveAs(HttpContext.Current.Server.MapPath(newPath));
            return newPath;
        }

        /// <summary>
        /// 执行批量文件上传。
        /// </summary>
        /// <param name="upload">HttpFileCollection文件集。</param>
        /// <param name="basePath">保存虚拟路径基目录，可用%%包含时间字串格式来根据时间动态生成基目录，不传时将已当前 Web 应用程序根目录为基目录。</param>
        /// <param name="maxSize">文件大小上限(KB单位)，设为 null 不限制大小。</param>
        /// <param name="extFilter">支持扩展名，用|分格(如".jpg|.jpeg|.gif|.bmp|.png"),设置为空时不限制文件类型。</param>
        /// <param name="errorMessage">out 上传失败时的错误信息。</param>
        /// <returns>保存文件虚拟路径。</returns>
        public static List<string> UpFile(HttpFileCollection upload, string basePath, int? maxSize, string extFilter, out string errorMessage)
        {
            errorMessage = string.Empty;           
            List<string> newFiles = new List<string>();

            //上传框数量
            int count = upload.Count;
            //用于保存文件扩展名           
            string[] exts = new string[count];
            //用于保存文件名。
            string[] files = new string[count];           
            //是否需要过滤上传文件类型。
            bool extCheck = !string.IsNullOrEmpty(extFilter);

            HttpPostedFile hpf;

            for (int i = 0; i < count; i++)
            {
                hpf = upload[i];
                if (string.IsNullOrEmpty(hpf.FileName)) continue;
                if (hpf.ContentLength == 0) continue;

                //检查类型
                if (extCheck)
                {
                    string fileExt = System.IO.Path.GetExtension(hpf.FileName);

                    if (!Regex.IsMatch(fileExt, extFilter, RegexOptions.IgnoreCase))
                    {
                        errorMessage = string.Format(RS.GetString("OnlySupportFileTypes"), extFilter);
                        return newFiles;
                    }

                    //保存扩展名
                    exts[i] = fileExt;
                    files[i] = hpf.FileName;
                }
                else
                {
                    //保存扩展名 
                    exts[i] = System.IO.Path.GetExtension(hpf.FileName);
                }
                //检查大小
                if (maxSize.HasValue)
                {
                    if (hpf.ContentLength > maxSize * 1024)
                    {
                        errorMessage = string.Format(RS.GetString("FileSizeLimit"), FormatBytes(1024L * maxSize.Value));
                        return newFiles;
                    }
                }
            }

            //文件上传
            for (int i = 0; i < count; i++)
            {
                hpf = upload[i];
                if (string.IsNullOrEmpty(hpf.FileName)) continue;

                //文件名
                string newPath = GetSavePath(basePath, hpf.FileName);
                hpf.SaveAs(HttpContext.Current.Server.MapPath(newPath));
                newFiles.Add(newPath);
            }

            return newFiles;
        }

        #endregion
    }
}